/*
 * Copyright (C) 2009-2011, iFLYTEK Ltd.
 *
 * All rights reserved.
 *
 */

package com.wzh.advancelog.log;

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.Log;

import com.google.gson.Gson;
import com.wzh.advancelog.log.db.LogEntity;
import com.wzh.advancelog.log.db.MyDatabase;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

/**
 * 日志类
 *
 * @author zhwang37
 * @date 2021-08-06
 */
@SuppressWarnings({"unused", "RedundantSuppression", "JavaDoc", "UnusedReturnValue"})
public final class WzhLog {
    public static final String TAG = WzhLog.class.getSimpleName();

    private static final int MAX_FILE_SIZE = 10 * 1024 * 1024; // 最大保存10M
    private static String mLogPath;
    @SuppressLint("StaticFieldLeak")
    private static Context mGlobalContext;

    public static Context getGlobalContext() {
        return mGlobalContext;
    }

    public static void init(Context globalContext) {
        mGlobalContext = globalContext;
        mLogPath = mGlobalContext.getExternalCacheDir() + File.separator
                + mGlobalContext.getPackageName() + File.separator + "log" + File.separator;
        // /storage/emulated/0/Android/data/com.example.wzh.rxdemo/cache/com.example.wzh.rxdemo/log/
    }

    protected static boolean mLoggingEnabled = true;
    protected static boolean mLoggingToFileEnabled = true;
    private static final LinkedBlockingDeque<LogEntity> logQueue = new LinkedBlockingDeque<>();

    private static void putLog(long logTime, String logLevel, String tag, String log) {
        try {
            logQueue.put(new LogEntity(logTime, logLevel, tag, log));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static int v(String tag, String msg) {
        int result = 0;
        if (!mLoggingEnabled) {
            return result;
        }
        msg = buildMessage(msg);
        result = Log.v(tag, msg);
        if (!mLoggingToFileEnabled) {
            return result;
        }
        saveLog(System.currentTimeMillis(), "v", tag, msg);
        return result;
    }

    public static int v(String tag, String msg, Throwable tr) {
        int result = 0;
        if (!mLoggingEnabled) {
            return result;
        }
        msg = buildMessage(msg);
        result = Log.v(tag, msg, tr);
        if (!mLoggingEnabled) {
            return result;
        }
        saveLog(System.currentTimeMillis(), "v", tag, msg, tr);
        return result;
    }

    /**
     * d级日志不输出到文件
     *
     * @param tag
     * @param msg
     * @return
     */
    @SuppressWarnings("ConstantConditions")
    public static int d(String tag, String msg) {
        int result = 0;
        if (!mLoggingEnabled) {
            return result;
        }
        msg = buildMessage(msg);
        result = Log.d(tag, msg);
        if (true) {
            return result;
        }
        saveLog(System.currentTimeMillis(), "d", tag, msg);
        return result;
    }

    @SuppressWarnings("ConstantConditions")
    public static int d(String tag, String msg, Throwable tr) {
        int result = 0;
        if (!mLoggingEnabled) {
            return result;
        }
        msg = buildMessage(msg);
        result = Log.d(tag, msg, tr);
        if (true) {
            return result;
        }
        saveLog(System.currentTimeMillis(), "d", tag, msg, tr);
        return result;
    }

    public static int i(String tag, String msg) {
        int result = 0;
        if (!mLoggingEnabled) {
            return result;
        }
        msg = buildMessage(msg);
        result = Log.i(tag, msg);
        if (!mLoggingToFileEnabled) {
            return result;
        }
        saveLog(System.currentTimeMillis(), "i", tag, msg);
        return result;
    }

    public static int i(String tag, String msg, Throwable tr) {
        int result = 0;
        if (!mLoggingEnabled) {
            return result;
        }
        msg = buildMessage(msg);
        result = Log.i(tag, msg, tr);
        if (!mLoggingToFileEnabled) {
            return result;
        }
        saveLog(System.currentTimeMillis(), "i", tag, msg, tr);
        return result;
    }

    public static int w(String tag, String msg) {
        int result = 0;
        if (!mLoggingEnabled) {
            return result;
        }
        msg = buildMessage(msg);
        result = Log.w(tag, msg);
        if (!mLoggingToFileEnabled) {
            return result;
        }
        saveLog(System.currentTimeMillis(), "w", tag, msg);
        return result;
    }

    public static int w(String tag, String msg, Throwable tr) {
        int result = 0;
        if (!mLoggingEnabled) {
            return result;
        }
        msg = buildMessage(msg);
        result = Log.w(tag, msg, tr);
        if (!mLoggingToFileEnabled) {
            return result;
        }
        saveLog(System.currentTimeMillis(), "w", tag, msg, tr);
        return result;
    }

    public static int e(String tag, String msg) {
        int result = 0;
        if (!mLoggingEnabled) {
            return result;
        }
        msg = buildMessage(msg);
        result = Log.e(tag, msg);
        if (!mLoggingToFileEnabled) {
            return result;
        }
        saveLog(System.currentTimeMillis(), "e", tag, msg);
        return result;
    }

    public static int e(String tag, String msg, Throwable tr) {
        int result = 0;
        if (!mLoggingEnabled) {
            return result;
        }
        msg = buildMessage(msg);
        result = Log.e(tag, msg, tr);
        if (!mLoggingToFileEnabled) {
            return result;
        }
        saveLog(System.currentTimeMillis(), "e", tag, msg, tr);
        return result;
    }

    private static String buildMessage(String msg) {
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        if (stackTraceElements.length < 5) {
            return msg;
        } else {
            StringBuffer stringBuffer = new StringBuffer();
            StackTraceElement stackTraceElement = stackTraceElements[4];
            stringBuffer.append(stackTraceElement.getClassName().substring(stackTraceElement.getClassName().lastIndexOf(".") + 1));
            stringBuffer.append(".");
            stringBuffer.append(stackTraceElement.getMethodName());
            stringBuffer.append("(");
            stringBuffer.append(stackTraceElement.getFileName());
            stringBuffer.append(":");
            stringBuffer.append(stackTraceElement.getLineNumber());
            stringBuffer.append(") ");
            stringBuffer.append(msg);
            return stringBuffer.toString();
        }
    }

    /**
     * 带有异常信息的保存
     */
    public synchronized static void saveLog(long logTime, String logLevel, String tag, String log
            , Throwable tr) {
        if (tr != null) {
            log += "\n" + Log.getStackTraceString(tr);
        }
        saveLog(logTime, logLevel, tag, log);
    }

    /**
     * 打印一个List的详情
     *
     * @param tag
     * @param message
     * @param list
     * @param <T>
     */
    public static <T> void logList(String tag, String message, List<T> list) {
        i(tag, "log list:" + message);
        if (list == null) {
            i(tag, "list is null");
            return;
        }
        if (list.size() == 0) {
            i(tag, "list is empty");
            return;
        }
        i(tag, "==========log list:" + list.getClass() + " start, size:" + list.size()
                + " ========== ");
        for (int i = 0; i < list.size(); i++) {
            String elementStr = "";
            try {
                elementStr = new Gson().toJson(list.get(i));
            } catch (Exception e) {
                e.printStackTrace();
                e(tag, "", e);
            }
            i(tag, i + ">>>" + elementStr);
        }
        i(tag, "==========log list:" + list.getClass() + " end=========");
    }

    public static <T> void logMap(String tag, String message, Map<String, T> map) {
        i(tag, "log map:" + message);
        if (map == null) {
            i(tag, "map is null");
            return;
        }
        if (map.size() == 0) {
            i(tag, "map is empty");
            return;
        }
        i(tag, "==========log map:" + map.getClass() + " start,size:" + map.size()
                + "==========");
        for (String key : map.keySet()) {
            String valueStr = "";
            try {
                valueStr = new Gson().toJson(map.get(key));
            } catch (Exception e) {
                e.printStackTrace();
                e(tag, "", e);
            }
            i(tag, "key>>>" + key + ">>>value>>>" + valueStr);
        }
        i(tag, "==========log map:" + map.getClass() + " end==========");
    }

    /**
     * 保存日志到数据库
     */
    public static void saveLog(long logTime, String logLevel, String tag, String log) {
        if (!mLoggingToFileEnabled) {
            return;
        }
        putLog(logTime, logLevel, tag, log);
        getSaveToDbThreadPool().execute(new Runnable() {
            @Override
            public void run() {
                LogEntity logEntity = null;
                try {
                    logEntity = logQueue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (logEntity != null) {
                    innerSaveLog(logEntity);
                }
            }
        });
    }

    @SuppressLint("ConstantLocale")
    private static final SimpleDateFormat SDF_FILE = new SimpleDateFormat(
            "yyyy-MM-dd HH-mm-ss", Locale.getDefault());
    @SuppressLint("ConstantLocale")
    private static final SimpleDateFormat SDF_LOG = new SimpleDateFormat(
            "yyyy-MM-dd HH:mm:ss:SSS", Locale.getDefault());

    public static void innerSaveLog(LogEntity logEntity) {
        if (!mLoggingToFileEnabled) {
            return;
        }
        if (logEntity == null) {
            return;
        }
        MyDatabase.getInstance().logDao().insert(logEntity);
        checkOutputToFileThreadPool();
    }


    private static ExecutorService saveToDbThreadPool;

    private static ExecutorService getSaveToDbThreadPool() {
        if (saveToDbThreadPool == null || saveToDbThreadPool.isShutdown()) {
            saveToDbThreadPool = Executors.newFixedThreadPool(3, new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r, "logToFile");
                    thread.setPriority(Thread.MIN_PRIORITY);
                    return thread;
                }
            });
        }
        return saveToDbThreadPool;
    }

    private static ScheduledExecutorService outputToFileThreadPool;

    private static void checkOutputToFileThreadPool() {
        if (outputToFileThreadPool == null || outputToFileThreadPool.isShutdown()) {
            outputToFileThreadPool = Executors.newSingleThreadScheduledExecutor();
            outputToFileThreadPool.scheduleWithFixedDelay(new Runnable() {
                @Override
                public void run() {
//                    WzhLog.d(TAG, "run out put to file");
                    long exportedTime;
                    exportedTime = PreferenceUtils.getInstance().getLong(PreferenceKey.EXPORTED_TIME, 0);
//                    WzhLog.d(TAG, "exportedTime:" + exportedTime);
                    List<LogEntity> logEntityList = MyDatabase.getInstance().logDao().getLogEntities(exportedTime);
//                    WzhLog.d(TAG, "取出的日志数据" + logEntityList);
                    for (LogEntity logEntity : logEntityList) {
                        writeToFile(logEntity);
                    }
//                    WzhLog.d(TAG, "run out put to file end");
                }
            }, 1, 100, TimeUnit.MILLISECONDS);
            //在每秒打印20次日志的情况下，设置delay为100毫秒刚好可以使此线程不至于无事可做
            //也不会造成数据库里面未导出到文件的记录堆积
            //日志打印太频繁了将其去掉
        }
    }

    private static void writeToFile(LogEntity logEntity) {
        if (logEntity == null) {
            return;
        }
        if (logEntity.isExported()) {
            return;
        }
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(SDF_LOG.format(logEntity.getLogTime()));
        stringBuilder.append(" ");
        stringBuilder.append(logEntity.getLogLevel());
        stringBuilder.append("/");
        stringBuilder.append(logEntity.getTag());
        stringBuilder.append(": ");
        stringBuilder.append(logEntity.getMessage());
        stringBuilder.append("\n");
        try {
            File f = getLogFile();
            writeString(f, stringBuilder.toString());
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        logEntity.setExported(true);
        PreferenceUtils.getInstance().putLong(PreferenceKey.EXPORTED_TIME, logEntity.getLogTime());
        MyDatabase.getInstance().logDao().update(logEntity);
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    private static File getLogFile() {
        File dir = new File(mLogPath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        File[] files = dir.listFiles();
        File outputFile;
        if (files == null || files.length == 0) {
            outputFile = new File(mLogPath +
                    SDF_FILE.format(new Date()) + ".log");
        } else if (files[files.length - 1].length() > MAX_FILE_SIZE) {
            outputFile = new File(mLogPath +
                    SDF_FILE.format(new Date()) + ".log");
        } else {
            outputFile = files[files.length - 1];
        }
        return outputFile;
    }

    private static void writeString(File outputFile, String text) throws IOException {
        FileOutputStream fos = new FileOutputStream(outputFile, true);
        fos.write(text.getBytes(StandardCharsets.UTF_8));
    }

    public static void clearOutDate() {
        saveToDbThreadPool.execute(new Runnable() {
            @SuppressWarnings("ResultOfMethodCallIgnored")
            @Override
            public void run() {
                File dir = new File(mLogPath);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                File[] files = dir.listFiles();
                if (files != null) {
                    for (File file : files) {
                        Date date;
                        try {
                            date = SDF_FILE.parse(file.getName().split("\\.")[0]);
                        } catch (ParseException e) {
                            date = new Date(0);
                            e.printStackTrace();
                        }
                        //超过七天的删除掉
                        if (System.currentTimeMillis() - (date != null ? date.getTime() : 0) > 3600 * 24 * 7) {
                            file.delete();
                        }
                    }
                }
            }
        });
    }

    @SuppressWarnings("ConstantConditions")
    public static void CheckCachedLogSize() {
        if (logQueue != null) {
            PreferenceUtils.getInstance().putInt(PreferenceKey.CACHED_LOG_COUNT, logQueue.size());
        }
    }
}
